package com.webex.ta.hydra.plugin.gui;

import com.webex.ta.hydra.gui.GuiPackage;
import com.webex.ta.hydra.gui.util.ComponentUtil;
import com.webex.ta.hydra.gui.util.RolloverButton;
import com.webex.ta.hydra.plugin.Plugin;
import com.webex.ta.hydra.plugin.PluginManager;
import com.webex.ta.hydra.util.HydraUtils;
import com.webex.ta.hydra.util.ProgressObserver;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-9-29
 * Time: 13:33:53
 */
public class PluginManagerDialog extends JDialog implements ChangeListener {
    private static final String UNINSTALL = "UNINSTALL";
    private static final String INSTALL = "INSTALL";
    private static final String UPGRADE = "UPGRADE";

    private JProgressBar progress = new JProgressBar();
    private JTable installed, available;
    private InstalledPluginModel installedModel;
    private AvailablePluginModel availableModel;
    private RolloverButton install, uninstall;
    private JTabbedPane tabbed;

    private JTextArea description = new JTextArea();
    private JLabel author = new JLabel("Author: ");

    private PluginManagerActionListener listener = new PluginManagerActionListener();

    public PluginManagerDialog(Frame owner, boolean modal) {
        super(owner, "Plugin Manager", modal);
        getRootPane().setLayout(new BorderLayout());
        getRootPane().add(createTopPane(), BorderLayout.NORTH);
        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        split.setBorder(new EmptyBorder(5, 15, 15, 15));
        split.setLeftComponent(createLeftPane());
        split.setRightComponent(createRightPane());
        split.setResizeWeight(0.7);
        getRootPane().add(split, BorderLayout.CENTER);
        installedModel.update();
    }

    private Component createTopPane() {
        JPanel p = new JPanel(new BorderLayout());
        Box box = Box.createHorizontalBox();
        install = makeToolButton(INSTALL, HydraUtils.getImage("install.png"));
        install.addActionListener(listener);
        uninstall = makeToolButton(UNINSTALL, HydraUtils.getImage("uninstall.png"));
        uninstall.addActionListener(listener);
        box.add(install);
        box.add(uninstall);
        box.add(Box.createGlue());
        box.add(progress);
        progress.setIndeterminate(true);
        p.setBorder(new EmptyBorder(5, 15, 0, 15));
        p.add(box, BorderLayout.CENTER);
        return p;
    }

    private Component createLeftPane() {
        JPanel p = new JPanel(new BorderLayout());
        tabbed = new JTabbedPane();
        tabbed.addChangeListener(this);
        tabbed.add("Installed", createInstalledPane());
        tabbed.add("Available", createAvailablePane());
        p.add(tabbed, BorderLayout.CENTER);
        return p;
    }

    private Component createInstalledPane() {
        installed = new JTable(installedModel = new InstalledPluginModel());
        installed.setShowGrid(false);
        installed.setIntercellSpacing(new Dimension(0, 0));
        installed.setRowHeight(installed.getRowHeight() + 2);
        installed.getColumnModel().getColumn(0).setResizable(false);
        installed.getColumnModel().getColumn(0).setMaxWidth(40);
        installed.getColumnModel().getColumn(0).setMinWidth(40);
        installed.setPreferredScrollableViewportSize(new Dimension(300, 300));
        installed.addMouseListener(new InstalledTableListener());
        JTableHeader header = installed.getTableHeader();
        header.setFont(new Font("Dialog", Font.PLAIN, 10));
        header.setReorderingAllowed(false);
        header.setDefaultRenderer(new HeaderRenderer(
                (DefaultTableCellRenderer) header.getDefaultRenderer()));
        return new JScrollPane(installed);
    }

    private Component createAvailablePane() {
        available = new JTable(availableModel = new AvailablePluginModel());
        available.setShowGrid(false);
        available.setIntercellSpacing(new Dimension(0, 0));
        available.setRowHeight(available.getRowHeight() + 2);
        available.getColumnModel().getColumn(0).setPreferredWidth(180);
        available.getColumnModel().getColumn(1).setPreferredWidth(120);
        available.getColumnModel().getColumn(2).setPreferredWidth(70);
        available.getColumnModel().getColumn(3).setPreferredWidth(70);
        available.setPreferredScrollableViewportSize(new Dimension(300, 300));
        available.addMouseListener(new AvailableTableListener());
        JTableHeader header = available.getTableHeader();
        header.setFont(new Font("Dialog", Font.PLAIN, 10));
        header.setReorderingAllowed(false);
        header.setDefaultRenderer(new HeaderRenderer(
                (DefaultTableCellRenderer) header.getDefaultRenderer()));
        return new JScrollPane(available);
    }

    private Component createRightPane() {
        JPanel p = new JPanel(new BorderLayout());
        p.add(author, BorderLayout.NORTH);
        description.setColumns(20);
        description.setLineWrap(true);
        description.setWrapStyleWord(true);
        JScrollPane scroll = new JScrollPane(description);
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        JPanel sub = new JPanel(new BorderLayout());
        sub.add(new JLabel("Description:"), BorderLayout.NORTH);
        sub.add(scroll, BorderLayout.CENTER);
        p.add(sub, BorderLayout.CENTER);
        return p;
    }

    private RolloverButton makeToolButton(String actionName, Icon icon) {
        RolloverButton btn = new RolloverButton(icon);
        btn.setMargin(new Insets(1, 1, 1, 1));
        btn.setBorderPainted(false);
        btn.setActionCommand(actionName);
        return btn;
    }

    private JPopupMenu createUpgradeMenu() {
        if (installed.getSelectedRow() >= 0) {
            JPopupMenu pop = new JPopupMenu();
            JMenuItem update = new JMenuItem("Update Plugin");
            update.setActionCommand(UPGRADE);
            update.addActionListener(listener);
            update.setIcon(HydraUtils.getImage("install.png"));
            JMenuItem uninstall = new JMenuItem("Uninstall");
            uninstall.setActionCommand(UNINSTALL);
            uninstall.addActionListener(listener);
            uninstall.setIcon(HydraUtils.getImage("uninstall.png"));
            pop.add(update);
            pop.add(uninstall);
            return pop;
        }
        return null;
    }

    private JPopupMenu createInstallMenu() {
        return null;
    }

    @Override
    public void show() {
        super.show();
        new Thread(new Runnable() {
            public void run() {
                try {
                    PluginManager.getInstance().downloadPluginList();
                } catch (Exception e) {
                    JOptionPane.showConfirmDialog(PluginManagerDialog.this, e.getMessage(),
                            "Download Plugin List Error", JOptionPane.OK_OPTION);
                }
                progress.setIndeterminate(false);
                progress.setVisible(false);
            }
        }).start();
    }

    private void updateStatus() {
        switch (tabbed.getSelectedIndex()) {
            case 0:
                if (installed.getSelectedRow() >= 0) {
                    Entry entry = installedModel.entries.get(installed.getSelectedRow());
                    install.setEnabled(PluginManager.getInstance().canUpdate(entry.name));
                    uninstall.setEnabled(true);
                    description.setText(entry.description);
                    author.setText("Author: " + entry.author);
                } else {
                    install.setEnabled(false);
                    uninstall.setEnabled(false);
                    description.setText("");
                    author.setText("Author: ");
                }
                break;
            case 1:
                uninstall.setEnabled(false);
                if (available.getSelectedRow() >= 0) {
                    Entry entry = availableModel.entries.get(available.getSelectedRow());
                    install.setEnabled(true);
                    description.setText(entry.description);
                    author.setText("Author: " + entry.author);
                } else {
                    install.setEnabled(false);
                    description.setText("");
                    author.setText("Author: ");
                }
                break;
        }
    }

    public void stateChanged(ChangeEvent e) {
        try {
            if (tabbed.getSelectedIndex() == 0) {
                install.setActionCommand(UPGRADE);
            } else if (tabbed.getSelectedIndex() == 1) {
                install.setActionCommand(INSTALL);
                availableModel.update();
            }
            updateStatus();
        } catch (Exception ignore) {
            ignore.printStackTrace();
        }
    }


    static class Entry {
        private boolean enabled;
        private String category;
        private String name;
        private String author;
        private String version;
        private int size;
        private String date;
        private String description;
        private boolean updateAvailable = false;

        public Entry(Plugin plugin) {
            name = plugin.getName();
            category = plugin.getCategory();
            author = plugin.getAuthor();
            version = plugin.getVersion();
            description = plugin.getDescription();
            enabled = plugin.isActivated();
            if (plugin.hasBranch()) {
                Plugin.Branch branch = plugin.getLatestBranch();
                this.size = branch.getSize();
                this.date = branch.getDate();
                this.version = branch.getVersion();
            }
        }
    }

    private class InstalledPluginModel extends AbstractTableModel {
        private List<Entry> entries = new ArrayList<Entry>();
//		private int sortType = EntryCompare.COLUMN_NAME;
        int sortDirection = 1;

        InstalledPluginModel() {
            entries = new ArrayList<Entry>();
        }

        public int getColumnCount() {
            return 3;
        }

        public Class getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return Boolean.class;
                default:
                    return Object.class;
            }
        }

        public String getColumnName(int column) {
            switch (column) {
                case 0:
                    return "Enable";
                case 1:
                    return "Name";
                case 2:
                    return "Version";
                default:
                    throw new Error("Column out of range");
            }
        }

        public int getRowCount() {
            return entries.size();
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            Entry entry = entries.get(rowIndex);
            switch (columnIndex) {
                case 0:
                    return Boolean.valueOf(entry.enabled);
                case 1:
                    return entry.name;
                case 2:
                    return entry.version;
                default:
                    throw new Error("Column out of range");
            }
        }

        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return columnIndex == 0;
        }

        public void setValueAt(Object value, int rowIndex, int columnIndex) {
            Entry entry = entries.get(rowIndex);
			if(columnIndex == 0) {
                boolean success;
                if (entry.enabled) {
                    int answer = JOptionPane.showConfirmDialog(PluginManagerDialog.this,
                        "Do you really want to disable plugin \"" + entry.name + "\"?");
                    if (answer != JOptionPane.YES_OPTION)
                        return;
                    success = PluginManager.getInstance().deactivate(entry.name);
                } else
                    success = PluginManager.getInstance().activate(entry.name);
                if (success)
                    entry.enabled = !entry.enabled;
                update();
            }

        }

        public void update() {
            entries.clear();

            Plugin[] plugins = PluginManager.getInstance().getInstalledPlugins();
            for (Plugin plugin : plugins) {
                entries.add(new Entry(plugin));
            }
            this.fireTableDataChanged();
//			sort(sortType);
//			restoreSelection(savedSelection);
        }
    }

    private class AvailablePluginModel extends AbstractTableModel {
        private List<Entry> entries = new ArrayList<Entry>();
//		private int sortType = EntryCompare.COLUMN_NAME;
        int sortDirection = 1;

        AvailablePluginModel() {
            entries = new ArrayList<Entry>();
        }

        public int getColumnCount() {
            return 4;
        }

        public Class getColumnClass(int columnIndex) {
            return Object.class;
        }

        public String getColumnName(int column) {
            switch (column) {
                case 0:
                    return "Name";
                case 1:
                    return "Category";
                case 2:
                    return "Version";
                case 3:
                    return "Size";
                case 4:
                    return "Release date";
                default:
                    throw new Error("Column out of range");
            }
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            Entry entry = entries.get(rowIndex);
            switch (columnIndex) {
                case 0:
                    return entry.name;
                case 1:
                    return entry.category;
                case 2:
                    return entry.version;
                case 3:
                    return entry.size;
                case 4:
                    return entry.date;
                default:
                    throw new Error("Column out of range");
            }
        }

        public int getRowCount() {
            return entries.size();
        }

        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return false;
        }

        public void update() {
            entries.clear();

            Plugin[] availablePlugins = PluginManager.getInstance().getAvailablePlugins();
            Plugin[] installedPlugins = PluginManager.getInstance().getInstalledPlugins();
            for (Plugin available : availablePlugins) {
                boolean alreadyInstalled = false;
                for (Plugin installed : installedPlugins) {
                    if (installed.getName().equalsIgnoreCase(available.getName())) {
                        alreadyInstalled = true;
                        break;
                    }
                }
                if (!alreadyInstalled)
                    entries.add(new Entry(available));
            }
            this.fireTableDataChanged();
        }
    }

    private static class HeaderRenderer extends DefaultTableCellRenderer {
        static final Icon ASC_ICON = HydraUtils.getImage("arrow-asc.png");
        static final Icon DESC_ICON = HydraUtils.getImage("arrow-desc.png");

        private DefaultTableCellRenderer tcr;

        HeaderRenderer(DefaultTableCellRenderer tcr) {
            this.tcr = tcr;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                                                       boolean isSelected, boolean hasFocus,
                                                       int row, int column) {
            JLabel l = (JLabel) tcr.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
//			PluginTableModel model = (PluginTableModel) table.getModel();
//			Icon icon = (column == model.sortType)
//				? (model.sortDirection == 1) ? ASC_ICON : DESC_ICON
//				: null;
            Icon icon = ASC_ICON;
            l.setIcon(icon);
            // l.setHorizontalTextPosition(l.LEADING);
            return l;
        }
    }

    private class PluginManagerActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            if (INSTALL.equals(cmd)) {
                // install
                if (available.getSelectedRow() < 0)
                    return;

                final Entry entry = availableModel.entries.get(available.getSelectedRow());
                int answer = JOptionPane.showConfirmDialog(PluginManagerDialog.this,
                        "Would you like download and install plugin \"" + entry.name + "\"?",
                        "Download and Install",
                        JOptionPane.YES_NO_OPTION);
                if (answer == JOptionPane.YES_OPTION) {
                    final DownloadMonitor monitor = new DownloadMonitor(GuiPackage.getInstance().getIdeFrame());
                    ComponentUtil.centerComponentInWindow(monitor);
                    monitor.setVisible(true);
                    Thread t = new Thread(new Runnable() {
                        public void run() {
                            boolean success = PluginManager.getInstance().install(entry.name, monitor);
                            monitor.setVisible(false);
                            if (success) {
                                installedModel.update();
                                availableModel.update();
                            }
                        }
                    });
                    t.start();
                }
            } else if (UNINSTALL.equals(cmd)) {
                // uninstall
                if (installed.getSelectedRow() < 0)
                    return;

                Entry entry = installedModel.entries.get(installed.getSelectedRow());
                int answer = JOptionPane.showConfirmDialog(PluginManagerDialog.this,
                        "Do you really want to uninstall plugin \"" + entry.name + "\"?",
                        "Uninstall",
                        JOptionPane.YES_NO_OPTION);
                if (answer == JOptionPane.YES_OPTION) {
                    PluginManager.getInstance().uninstall(entry.name);
                    installedModel.update();
                    availableModel.update();
                }
            } else if (UPGRADE.equals(cmd)) {
                // upgrade
                if (installed.getSelectedRow() >= 0) {
                    Entry entry = installedModel.entries.get(installed.getSelectedRow());
                    System.out.println("UPGRADE " + entry.name);
                }
            }
        }
    }

    private class InstalledTableListener extends MouseAdapter {
        protected void processEvent(MouseEvent e) {
            if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
                int modifiers = e.getModifiers();
                modifiers -= MouseEvent.BUTTON3_MASK;
                modifiers |= MouseEvent.BUTTON1_MASK;
                MouseEvent ne = new MouseEvent(e.getComponent(), e.getID(),
                        e.getWhen(), modifiers, e.getX(), e
                                .getY(), e.getClickCount(), false);
                installed.dispatchEvent(ne);
            }
        }

        @Override
        public void mousePressed(MouseEvent evt) {
            if (installed.getSelectedRowCount() <= 1 && evt.getModifiers() == Event.META_MASK)
                processEvent(evt);

            updateStatus();
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() < 2) {
                JPopupMenu contextMenu = createUpgradeMenu();
                if (contextMenu != null) {
                    contextMenu.show((Component) e.getSource(), e.getX(), e.getY());
                }
            }
        }
    }

    private class AvailableTableListener extends MouseAdapter {
        protected void processEvent(MouseEvent e) {
            if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
                int modifiers = e.getModifiers();
                modifiers -= MouseEvent.BUTTON3_MASK;
                modifiers |= MouseEvent.BUTTON1_MASK;
                MouseEvent ne = new MouseEvent(e.getComponent(), e.getID(),
                        e.getWhen(), modifiers, e.getX(), e
                                .getY(), e.getClickCount(), false);
                available.dispatchEvent(ne);
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() < 2) {
                JPopupMenu contextMenu = createUpgradeMenu();
                if (contextMenu != null) {
                    contextMenu.show((Component) e.getSource(), e.getX(), e.getY());
                }
            }
        }

        @Override
        public void mousePressed(MouseEvent evt) {
            if (available.getSelectedRowCount() <= 1 && evt.getModifiers() == Event.META_MASK)
                processEvent(evt);

            updateStatus();
        }
    }

    public static class DownloadMonitor extends JFrame implements ProgressObserver, ActionListener {
        private JButton cancel = new JButton("Cancel");
        private JProgressBar progress = new JProgressBar();
        private JLabel status = new JLabel(" ");

        public DownloadMonitor(Frame owner) {
            super();
            this.setAlwaysOnTop(true);
            JPanel p;
            progress.setPreferredSize(new Dimension(300, 21));
            cancel.setPreferredSize(new Dimension(80, 21));
            getRootPane().setLayout(new BorderLayout());
            p = new JPanel();
            p.add(status);
            getRootPane().add(p, BorderLayout.NORTH);
            getRootPane().add(progress, BorderLayout.CENTER);
            p = new JPanel();
            p.add(cancel);
            getRootPane().add(p, BorderLayout.SOUTH);
            pack();
            this.setResizable(false);
            cancel.addActionListener(this);
        }

        public void setMaximum(int value) {
            progress.setMaximum(value);
        }

        public void setValue(int value) {
            progress.setValue(value);
        }

        public void setStatus(String status) {
            this.status.setText(status);
        }

        public void finish() {
            this.setVisible(false);
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == cancel) {
                PluginManager.getInstance().cancelInstall();
            }
        }
    }
}
